package wortubung;

import static java.lang.Math.max;
import static java.lang.System.out;
import static org.junit.Assert.fail;
import static wortubung.util.CommonUtils.*;
import static wortubung.util.MatrixPrinter.*;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.junit.Before;

import wortubung.core.LANG;
import wortubung.core.WORD;
import wortubung.inflection.ConjugationResult;
import wortubung.inflection.InflectionError;
import wortubung.inflection.VerbConjugation;
import wortubung.lang.LanguageManager;
import wortubung.util.ToolBase;
import wortubung.util.TransliterateException;

public abstract class ConjugationTestBase {

	protected abstract LANG getLanguage();
	protected abstract VerbConjugation conjugate(String verb);

	protected String[] verbGroupJSONFilenames;
	protected String[] allVerbGroups;

	protected HashMap<String,ConjugationResult[]> verbGroupMap = new HashMap<String,ConjugationResult[]>();
	protected HashMap<String,String> verbGroupTitleMap = new HashMap<String,String>();
	protected int longestVerbLength = 0;

	ConjugationTestBase(String[] jsonFileNames, String[] verbGroups) {
		this.verbGroupJSONFilenames = jsonFileNames;
		this.allVerbGroups = verbGroups;
		
		LanguageManager.getAllManagers(ToolBase.getSingleton());
	}

	/**
	 * This approach is correct.
	 * Another way would be to instantiate this in constructor.
	 * But we don't know how JUnit creates the test classes, and how many instances will be created.
	 * Using static is not good either, as this is a base class, so it can be used by different languages.
	 */
	@Before
	public void setup() throws Exception {
		if (verbGroupMap.isEmpty()) {
			for (String fname : verbGroupJSONFilenames) {
	    		String jsonStr = readFileFromCPAsString(fname);
	    		int idx = jsonStr.indexOf(JSONConsts.__CUT__.name());
	    		if (idx > 0)
	    			jsonStr = jsonStr.substring(0, idx);
	    		JSONObject json = new JSONObject(jsonStr);
	    		for (String group : allVerbGroups)
	    			readVerbs(json, group);
			}
		}
	}

	protected void readVerbs(JSONObject json, String varName) throws JSONException, TransliterateException {
		if (!json.has(varName))
			return;
		JSONArray arr = json.getJSONArray(varName);
		ArrayList<ConjugationResult> ret = new ArrayList<ConjugationResult>();
		for (int i=0; i<arr.length(); ++i) {
			VerbConjugation vc = ConjugationResult.fromJSON(arr.getJSONObject(i), getLanguage());
			if (vc != null) {
				ConjugationResult cr = vc.getConjugationResult(null);
				ret.add(cr);
				longestVerbLength = max(longestVerbLength, length(cr.getVerb()));
			}
		}
		verbGroupMap.put(varName, ret.toArray(new ConjugationResult[ret.size()]));
		verbGroupTitleMap.put(varName, json.getString(varName + "_title"));
	}

	protected void checkVerbGroup(String varName) {
		checkVerbGroup(varName, null);
	}

	protected void checkVerbGroup(String varName, String verbToCheck) {
		ArrayList<String[]> errors = new ArrayList<String[]>();
		try {
			verbToCheck = getLanguage().toVernacular(verbToCheck);
		} catch (TransliterateException e1) {
			throw new RuntimeException(e1);
		}

		// Check all or just verbToCheck is provided.
		for (ConjugationResult cr : verbGroupMap.get(varName)) {
			String verb = cr.getVerb();
			if (verbToCheck == null) { // check all.
				cr.checkError(errors, getLanguage(), conjugate(cr.getVerb()).getConjugationResult(null));
			} else if (eq(verbToCheck, verb, false)) {
				cr.checkError(errors, getLanguage(), conjugate(verbToCheck).getConjugationResult(null));
				verbToCheck = null;
				break;
			}
		}
		if (verbToCheck != null)
			fail("Verb '" + verbToCheck + "' is not found in group '" + varName + "'.");
		if (errors.size() == 0)
			return;

		// Now print the report:
		ArrayList<String> verbs = new ArrayList<String>();
		String lastVerb = "";
		for (String[] e : errors) {
//			getLanguage().toLatin(e, true);
			if (e[0].equals(lastVerb))
				e[0] = null;
			else
				verbs.add(lastVerb = e[0]);
		}
		printMatrix(null, true, InflectionError.titles, errors.toArray(new String[errors.size()][]));
		String[] sa = verbs.toArray(new String[verbs.size()]);
		getLanguage().toLatin(sa, true);
		fail("Verb(s) with bad conjugations: " + concat(',', sa));
	}

	/////////////////////////////////////////////////
	// Informational
	//

	protected void doListAllVerbs(boolean unverifiedOnly, String[] groups) {
		int cnt = 0;
		for (String group : groups)
			cnt += listVerbs(unverifiedOnly, group);
		int allCnt = 0;
		for (ConjugationResult[] group : verbGroupMap.values())
			allCnt += length(group);
		if (cnt > 0)
			out.println("Total unverified/all verbs: " + cnt + "/" + allCnt + '\n');
		out.flush();
	}

	/**
	 * @return number of unverified verbs
	 */
	protected int listVerbs(boolean unverifiedOnly, String varName) {
		int cnt = 0;
		ArrayList<Object> verbsToPrint = new ArrayList<Object>();
		for (ConjugationResult cr : verbGroupMap.get(varName)) {
			boolean isVerified = cr.isVerified();
			if (!isVerified)
				++cnt;
			if (!unverifiedOnly || !isVerified) {
				String altFlag = cr.hasAlternative() ? "2" : " ";
				String verFlag = isVerified ? "+" : " ";
				verbsToPrint.add(altFlag + verFlag + getLanguage().toLatin(cr.getVerb(), true));
			}
		}
		if (!verbsToPrint.isEmpty())
			LanguageManager.get(getLanguage()).getToolBase().printToPageWidth(verbsToPrint, 2);
		return cnt;
	}

	/**
	 * @return { untested, total }
	 */
	protected void doCheckUntestedBuiltinVerbs() {
		LanguageManager lm = LanguageManager.get(getLanguage());
		HashSet<String> tests = new HashSet<String>();
		for (ConjugationResult[] crs : verbGroupMap.values())
			for (ConjugationResult cr : crs)
				tests.add(cr.getVerb());

		int cnt = 0;
		Collection<WORD> words = lm.getWords();
		ArrayList<Object> tested = new ArrayList<Object>();
		ArrayList<Object> untested = new ArrayList<Object>();
		for (WORD v : words) {
			if (v.isVerb()) {
				++cnt;
				String verbText = v.getWord();
				if (!tests.contains(verbText)) {
					untested.add(lm.toLatin(verbText, true));
				} else {
					tested.add(lm.toLatin(verbText, true));
					tests.remove(verbText);
				}
			}
		}
//		if (!tested.isEmpty()) {
//			out.println("Tested ----");
//			lm.getToolBase().printToPageWidth(tested, 2);
//		}
		if (!untested.isEmpty()) {
			out.println("Untested ----");
			lm.getToolBase().printToPageWidth(untested, 2);
		}
		if (!tests.isEmpty()) {
			out.println("Non-included test verbs ----");
			String[] x = tests.toArray(new String[tests.size()]);
			lm.getLanguage().toLatin(x,true);
			lm.getToolBase().printToPageWidth(x, 2);
		}

		out.println("Untested/total verbs: " + untested.size() + "/" + cnt + '\n');
	}

	/**
	 * Useful for reformatting the test data for alignment
	 */
	protected void doReprintVerbGroupJSON(String[] allGroups) {
		final String REPRINT_JSON = "REPRINT_JSON";

		String reprintLang = System.getProperty(REPRINT_JSON);
		if (null == reprintLang) {
			out.println();
			out.println("<*> HINT: to reprint the JSON values for " + getLanguage().getEnglishName() + ", specify -D" + REPRINT_JSON + "=" + getLanguage().name());
			out.println();
		} else if (reprintLang.equals(getLanguage().name())) {
			String lastGroup = allGroups[allGroups.length-1];
			out.print("{\n\n");
			for (String group : allGroups) {
				String title = verbGroupTitleMap.get(group);
				if (isNotEmpty(title))
					out.print(group + "_title: \"" + title + "\",\n");
				out.print(group + ":\n");
				printVerbGroup(group, group.equals(lastGroup));
			}
			out.print("}\n");
			out.flush();
		}
	}

	protected void printVerbGroup(String varName, boolean isLast) {
		String json = LanguageManager.get(getLanguage()).conjugationSuiteToJSON(verbGroupMap.get(varName), getLanguage());
		out.println(json);
		out.print(isLast ? "\n\n" : ",\n\n");
	}
}
